// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Xml;
using System.Xml.XPath;

using Microsoft.TeamFoundation.Migration.Toolkit.Eventing;
using Microsoft.TeamFoundation.Migration.Toolkit.Linking;
using Microsoft.TeamFoundation.Migration.Toolkit.Wit;
using WIT = Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace Microsoft.TeamFoundation.Migration.Toolkit
{
    /// <summary>
    /// WIT-specific migration session.
    /// </summary>
    public class WorkItemTrackingSession: SettingsSection, IMigrationSession, IEventLogger
    {
        /// <summary>
        /// Session state.
        /// </summary>
        private enum SessionState
        {
            NotStarted = 0x01,          // Never started
            Running = 0x02,             // Is running
            Paused = 0x04,              // Is paused
            Complete = 0x08,            // Is complete
            Aborted = 0x10,             // Is aborted
        };

        private string m_id;                                // Session id
        private ReadOnlyCollection<WorkItemTypeMapping> m_typeMappings;  // Work item type mappings.
        private TfsMigrationDataSource m_tfsSource;         // TFS data source
        private OtherMigrationDataSource m_otherSource;     // Other data source
        private QueueConfiguration m_slowQueueConfig;       // Slow queue configuration
        private QueueConfiguration m_fastQueueConfig;       // Fast queue configuration
        private WitPolicies m_policies;                     // Policies
        private bool m_ignoreRevisions;                     // Ignore data from historical revisions
        private int m_sleepTime;                            // Seconds to sleep between passes
        private LinkingData m_linkData;                     // Linking data
        private List<IMigrationSessionEventSink> m_cleanup; // List of objects to clean up

        private object m_storesLock;                        // Synchronization object for work item stores
        private object m_mapperLock;                        // Synchronization object for the maper
        private SystemPair<IMigrationWorkItemStore> m_stores;   // Work item stores
        private Mapper m_mapper;                            // Work items mapper
        private IFieldValueTransformer m_valueTransformer;  // Field value transformer

        private object m_lock;                              // Lock object for synchronizing access to the process
        private SessionState m_state;                       // Session state
        private SyncProcess m_syncProcess;                  // Synchronization process

        /// <summary>
        /// Returns session id from the internal database.
        /// </summary>
        public int InternalId { get { return Mapper.SessionId; } }

        /// <summary>
        /// Session id.
        /// </summary>
        public string Id { get { return m_id; } }

        /// <summary>
        /// Tells whether the engine will ignore historical revisions.
        /// </summary>
        public bool IgnoreRevisions 
        { 
            get { return m_ignoreRevisions; } 
            internal set { m_ignoreRevisions = value; } 
        }

        /// <summary>
        /// Slow queue configuration parameters.
        /// </summary>
        public QueueConfiguration SlowQueueConfig { get { return m_slowQueueConfig; } }

        /// <summary>
        /// Fast queue configuration parameters.
        /// </summary>
        public QueueConfiguration FastQueueConfig { get { return m_fastQueueConfig; } }

        /// <summary>
        /// Gets mappings for work item types.
        /// </summary>
        public ReadOnlyCollection<WorkItemTypeMapping> WorkItemTypeMappings { get { return m_typeMappings; } }

        /// <summary>
        /// Returns linking engine.
        /// </summary>
        public ILinkEngine LinkEngine { get { return m_linkData == null? null : m_linkData.Engine; } }

        /// <summary>
        /// TFS data.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tfs")]
        public TfsMigrationDataSource TfsMigrationSource { get { return m_tfsSource; } }

        /// <summary>
        /// Provider-specific data.
        /// </summary>
        public OtherMigrationDataSource OtherMigrationSource { get { return m_otherSource; } }

        /// <summary>
        /// Returns policies.
        /// </summary>
        public WitPolicies Policies { get { return m_policies; } }

        /// <summary>
        /// Returns number of seconds to sleep between passes.
        /// </summary>
        public int SleepTime { get { return m_sleepTime; } internal set { m_sleepTime = value; } }

        /// <summary>
        /// Gets a field value transformer.
        /// </summary>
        public IFieldValueTransformer FieldValueTransformer 
        { 
            get { return m_valueTransformer; }
            internal set
            {
                Debug.Assert(value != null, "Value transformer is null!");
                m_valueTransformer = value;
            }
        }

        /// <summary>
        /// Retutns the helper object for logging custom events.
        /// </summary>
        public LogHelper LogHelper { get; private set; }

        // Session-specific events
        public event EventHandler<MigrationSessionEventArgs> SessionStart;
        public event EventHandler<MigrationSessionEventArgs> SessionPaused;
        public event EventHandler<MigrationSessionEventArgs> SessionResumed;
        public event EventHandler<MigrationSessionEventArgs> SessionAborted;
        public event EventHandler<MigrationSessionEventArgs> SessionComplete;
        public event EventHandler<MigrationSessionEventArgs> SessionError;
        public event EventHandler<CustomMigrationEventArgs> CustomEvent;

        // Work item-specific events
        public event EventHandler<WorkItemMigrationEventArgs> WorkItemMigrationStart;
        public event EventHandler<MigrationCompleteEventArgs> WorkItemMigrationComplete;
        public event EventHandler<AnalysisCompleteEventArgs> WorkItemAnalysisComplete;
        public event EventHandler<WorkItemMigrationEventArgs> WorkItemMigrationFailed;
        public event EventHandler<FieldConflictEventArgs> WorkItemFieldConflict;
        public event EventHandler<CollectionEventArgs> WorkItemAttachmentsOutOfSync;
        public event EventHandler<CollectionEventArgs> WorkItemLinksOutOfSync;
        public event EventHandler<MissingUserEventArgs> MissingUser;

        public bool IsComplete { get { return m_state == SessionState.Complete; } }
        public bool IsAborted { get { return m_state == SessionState.Aborted; } }
        public bool IsRunning { get { return m_syncProcess != null && m_state == SessionState.Running;  } }
        public bool IsPaused { get { return m_syncProcess != null && m_state == SessionState.Paused; } }

        /// <summary>
        /// Starts the session in the service mode. Synchronization process works in a separate
        /// thread and can be controlled by Pause, Resume, and Abort methods.
        /// </summary>
        /// <param name="mode">Says what will be processed</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void Start(
            SyncObjects mode)
        {
            lock (m_lock)
            {
                if (m_state == SessionState.Paused || m_state == SessionState.Running)
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorSessionAlreadyRunning);
                }

                try
                {
                    m_syncProcess = new SyncProcess(mode, this, SystemType.Tfs);
                    FireEvent(SessionStart, MigrationToolkitResources.SessionStarting);
                    m_syncProcess.Start();
                    m_state = SessionState.Running;
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Starts the synchronization session for work items.
        /// </summary>
        public void Start()
        {
            Start(SyncObjects.Artifacts);
        }

        /// <summary>
        /// Pauses the migration session.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void Pause()
        {
            lock (m_lock)
            {
                if (!IsRunning)
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorPausingSession);
                }

                try
                {
                    m_syncProcess.Pause();
                    m_state = SessionState.Paused;
                    FireEvent(SessionPaused, MigrationToolkitResources.SessionPaused);
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Resumes paused migration session.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void Resume()
        {
            lock (m_lock)
            {
                if (!IsPaused)
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorResumingSession);
                }

                try
                {
                    m_syncProcess.Resume();
                    m_state = SessionState.Running;
                    FireEvent(SessionResumed, MigrationToolkitResources.SessionResumed);
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Aborts the migration session.
        /// </summary>
        public void Abort()
        {
            lock (m_lock)
            {
                if (!(IsRunning || IsPaused))
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorAbortingSession);
                }

                AbortImpl();
            }
        }

        /// <summary>
        /// Stops the migration session.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void Stop()
        {
            lock (m_lock)
            {
                if (!(IsRunning || IsPaused))
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorCompletingSession);
                }

                try
                {
                    m_syncProcess.Complete();
                    OnSessionComplete();
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Registers the event sink.
        /// </summary>
        /// <param name="sink">Event sink</param>
        public void RegisterEventSink(IMigrationSessionEventSink sink)
        {
            if (sink != null)
            {
                sink.RegisterSession(this);
                m_cleanup.Add(sink);
            }
        }

        /// <summary>
        /// Does a single synchronization pass for work items.
        /// </summary>
        /// <param name="source">System which will be queried for new and updated work items</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void Synchronize(
            SystemType primarySystem)
        {
            lock (m_lock)
            {
                if (IsRunning || IsPaused)
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorSessionAlreadyRunning);
                }

                try
                {
                    m_syncProcess = new SyncProcess(SyncObjects.Artifacts, this, primarySystem);
                    FireEvent(SessionStart, MigrationToolkitResources.SessionStarting);
                    m_state = SessionState.Running;
                    m_syncProcess.SinglePass();
                    OnSessionComplete();
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Does a two-way synchronizaion pass for work items
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void SynchronizeFull()
        {
            lock (m_lock)
            {
                if (IsRunning || IsPaused)
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorSessionAlreadyRunning);
                }
                try
                {
                    m_syncProcess = new SyncProcess(SyncObjects.Artifacts, this, SystemType.Tfs);
                    FireEvent(SessionStart, MigrationToolkitResources.SessionStarting);
                    m_state = SessionState.Running;
                    m_syncProcess.DoublePass();
                    OnSessionComplete();
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Synchronizes work items with given ids.
        /// </summary>
        /// <param name="sourceSystem">System that contains given items</param>
        /// <param name="ids">Ids of items that should be synchronized</param>
        public void Synchronize(
            SystemType sourceSystem,
            params string[] ids)
        {
            lock (m_lock)
            {
                if (IsRunning || IsPaused)
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorSessionAlreadyRunning);
                }
                try
                {
                    m_syncProcess = new SyncProcess(SyncObjects.Artifacts, this, sourceSystem);
                    FireEvent(SessionStart, MigrationToolkitResources.SessionStarting);
                    m_state = SessionState.Running;
                    m_syncProcess.Synchronize(ids);
                    OnSessionComplete();
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Processes all deferred links for this session.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void ProcessDeferredLinks()
        {
            lock (m_lock)
            {
                if (IsRunning || IsPaused)
                {
                    throw new MigrationException(MigrationToolkitResources.ErrorSessionAlreadyRunning);
                }
                try
                {
                    m_syncProcess = new SyncProcess(SyncObjects.Links, this, SystemType.Tfs);
                    FireEvent(SessionStart, MigrationToolkitResources.SessionStarting);
                    m_state = SessionState.Running;
                    m_syncProcess.SinglePass();
                    OnSessionComplete();
                }
                catch (Exception e)
                {
                    OnSessionError(e);
                }
            }
        }

        /// <summary>
        /// Represents session as a string.
        /// </summary>
        /// <returns>String representation of the session</returns>
        public override string ToString()
        {
            return string.Format(
                CultureInfo.InvariantCulture,
                "WIT Session: {0}",             // Do not localize!
                m_id);
        }

        #region IDisposable Members

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // dispose managed resources
                    for (int i = 0; i < m_cleanup.Count; i++)
                    {
                        m_cleanup[i].Dispose();
                    }

                    lock (m_lock)
                    {
                        if (m_syncProcess != null)
                        {
                            AbortImpl();
                        }
                    }
                }

                // release unmanged resources
            }

            disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        bool disposed;

        #endregion

        /// <summary>
        /// Returns a pair of stores used by the session.
        /// </summary>
        [DebuggerHidden]
        internal SystemPair<IMigrationWorkItemStore> Stores
        {
            get
            {
                if (m_stores == null)
                {
                    lock (m_storesLock)
                    {
                        if (m_stores == null)
                        {
                            m_stores = new SystemPair<IMigrationWorkItemStore>(
                                TfsMigrationSource.CreateWorkItemStore(),
                                OtherMigrationSource.CreateWorkItemStore());
                        }
                    }
                }
                return m_stores;
            }
        }

        /// <summary>
        /// Returns work item mapper used by the session.
        /// </summary>
        [DebuggerHidden]
        internal Mapper Mapper
        {
            get
            {
                if (m_mapper == null)
                {
                    lock (m_mapperLock)
                    {
                        if (m_mapper == null)
                        {
                            m_mapper = new Mapper(Stores);
                        }
                    }
                }
                return m_mapper;
            }
        }

        /// <summary>
        /// Returns synchronization process used in the service mode.
        /// </summary>
        [DebuggerHidden]
        internal SyncProcess SyncProcess
        {
            get
            {
                if (m_syncProcess == null)
                {
                    lock (m_lock)
                    {
                        if (m_syncProcess == null)
                        {
                            m_syncProcess = new SyncProcess(SyncObjects.Artifacts, this, SystemType.Tfs);
                        }
                    }
                }
                return m_syncProcess;
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        private WorkItemTrackingSession()
        {
            m_lock = new object();
            m_storesLock = new object();
            m_mapperLock = new object();
            m_state = SessionState.NotStarted;
            m_cleanup = new List<IMigrationSessionEventSink>();
            LogHelper = new Eventing.LogHelper(this);
        }

        /// <summary>
        /// Initializes session from the XML.
        /// </summary>
        /// <param name="nav">XML navigator</param>
        /// <returns>Session object</returns>
        internal static WorkItemTrackingSession Load(
            XPathNavigator nav)
        {
            // Initialize session
            WorkItemTrackingSession session = new WorkItemTrackingSession();

            session.m_id = nav.GetAttribute("id", string.Empty);
            session.m_ignoreRevisions = XmlConvert.ToBoolean(nav.GetAttribute("ignoreRevisions", string.Empty));
            session.m_sleepTime = XmlConvert.ToInt32(nav.GetAttribute("sleepTime", string.Empty));

            // Initialize TFS provider
            XPathNavigator tfsNode = nav.SelectSingleNode("Tfs");
            session.m_tfsSource = new TfsMigrationDataSource(tfsNode, session);

            // Initialize non-TFS provider
            XPathNavigator otherNode = nav.SelectSingleNode("Source");
            session.m_otherSource = new OtherMigrationDataSource(otherNode, session);

            // Initialize read queues
            session.m_fastQueueConfig = QueueConfiguration.Create(
                QueueType.Read,
                nav.SelectSingleNode("FastQueue"));
            session.m_slowQueueConfig = QueueConfiguration.Create(
                QueueType.Read,
                nav.SelectSingleNode("SlowQueue"));

            // Initialize settings
            session.InitFromXml(nav.Select("Settings/Setting"));

            // Initialize type mappings
            XPathNodeIterator mappings = nav.Select("WorkItemTypes/WorkItemType");
            List<WorkItemTypeMapping> list = new List<WorkItemTypeMapping>(mappings.Count);
            foreach (XPathNavigator type in mappings)
            {
                WorkItemTypeMapping mapping = new WorkItemTypeMapping(type);
                list.Add(mapping);
            }
            session.m_typeMappings = list.AsReadOnly();
            session.m_policies = new WitPolicies(nav.SelectSingleNode("Policies"));

            // Initialize linking session
            XPathNavigator linkNav = nav.SelectSingleNode("Linking");
            if (linkNav != null)
            {
                session.m_linkData = new LinkingData(linkNav);
                session.m_linkData.WorkItemTrackingSession = session.m_id;
            }

            // Load event sinks
            XPathNodeIterator sinkNodes = nav.Select("EventSinks/EventSink");
            foreach (XPathNavigator sinkNode in sinkNodes)
            {
                string name = sinkNode.GetAttribute("provider", string.Empty);
                MigrationProvider prov = MigrationConfiguration.Providers[name];
                IMigrationSessionEventSink sink = (IMigrationSessionEventSink)prov.CreateInstance(
                    typeof(IMigrationSessionEventSink),
                    sinkNode.InnerXml);
                session.RegisterEventSink(sink);
            }

            // Initialize value transformer
            XPathNavigator trNav = nav.SelectSingleNode("FieldValueTransformer");
            session.m_valueTransformer = CreateFieldValueTransformer(trNav);

            return session;
        }

        /// <summary>
        /// Creates a field value transformer from the XML.
        /// </summary>
        /// <param name="nav">XML</param>
        /// <returns>Field value transformer</returns>
        private static IFieldValueTransformer CreateFieldValueTransformer(
            XPathNavigator nav)
        {
            IFieldValueTransformer res;
            if (nav == null)
            {
                res = new DefaultValueTransformer();
            }
            else
            {
                string name = nav.GetAttribute("provider", string.Empty);
                MigrationProvider prov = MigrationConfiguration.Providers[name];
                res = (IFieldValueTransformer)prov.CreateInstance(typeof(IFieldValueTransformer), string.Empty);
            }
            return res;
        }

        /// <summary>
        /// Finds reflected items.
        /// </summary>
        /// <param name="sourceSystem">Source system</param>
        /// <param name="ids">Ids of work items in the source system</param>
        /// <returns>Ids of work items in the other system</returns>
        public string[] FindReflections(
            SystemType sourceSystem,
            params string[] ids)
        {
            return Mapper.FindReflections(sourceSystem, ids);
        }


        #region Session events

        /// <summary>
        /// Fires an event.
        /// </summary>
        /// <param name="handler">Event handler</param>
        /// <param name="description">Event description</param>
        private void FireEvent(
            EventHandler<MigrationSessionEventArgs> handler,
            string description)
        {
            if (handler != null)
            {
                MigrationSessionEventArgs args = new MigrationSessionEventArgs(
                    description,
                    m_syncProcess.Stats.ItemsSynchronized,
                    m_syncProcess.Stats.ItemsFailed);
                handler(this, args);
            }
        }

        #endregion

        /// <summary>
        /// Fires a custom event.
        /// </summary>
        /// <param name="args">Arguments</param>
        void IEventLogger.FireCustomEvent(
            Eventing.CustomMigrationEventArgs args)
        {
            if (CustomEvent != null)
            {
                CustomEvent(this, args);
            }
        }

        /// <summary>
        /// Fires the "work item migration complete" event.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="sourceId">Source work item's id</param>
        /// <param name="targetId">Target work item's id</param>
        /// <param name="sourceRevisions">Number of revisions migrated from the source side</param>
        /// <param name="targetRevisions">Number of revisions created on the target side</param>
        internal void FireMigrationComplete(
            SyncProcess sharedData,
            FullId sourceId,
            FullId targetId,
            int sourceRevisions,
            int targetRevisions)
        {
            sharedData.Stats.IncrementSynchronizedItems();
            if (WorkItemMigrationComplete != null)
            {
                MigrationCompleteEventArgs args = new MigrationCompleteEventArgs(
                    sharedData.StoreTypes.Left,
                    sourceId, targetId, sourceRevisions, targetRevisions);

                WorkItemMigrationComplete(this, args);
            }
        }

        /// <summary>
        /// Fires the "work item analysis complete" event.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="sourceId">Source work item's id</param>
        /// <param name="targetId">Target work item's id, null if a new item is to be created</param>
        /// <param name="sourceRevisions">Number of revisions to be migrated from the source side</param>
        /// <param name="targetRevisions">Number of revisions to be created on the target side</param>
        internal void FireAnalysisComplete(
            SyncProcess sharedData,
            FullId sourceId,
            FullId targetId,
            int sourceRevisions,
            int targetRevisions)
        {
            if (WorkItemAnalysisComplete != null)
            {
                AnalysisCompleteEventArgs args = new AnalysisCompleteEventArgs(
                    sharedData.StoreTypes.Left, sourceId, targetId, sourceRevisions, targetRevisions);

                WorkItemAnalysisComplete(this, args);
            }
        }

        /// <summary>
        /// Fires the "work item migration start" event.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="sourceId">Work item id</param>
        internal void FireMigrationStart(
            SyncProcess sharedData,
            FullId sourceId)
        {
            if (WorkItemMigrationStart != null)
            {
                string msg = string.Format(
                    Resources.Culture,
                    Resources.MsgItemMigrationStart,
                    sourceId);

                WorkItemMigrationEventArgs args = new WorkItemMigrationEventArgs(sharedData.StoreTypes.Left, sourceId, msg);
                WorkItemMigrationStart(this, args);
            }
        }

        /// <summary>
        /// Fires session failure error.
        /// </summary>
        /// <param name="e">Exception</param>
        internal void FireSessionError(
            Exception e)
        {
            if (SessionError != null)
            {
                MigrationSessionEventArgs args = new MigrationSessionEventArgs(Resources.MsgEngineFailure, 0, 0, e);
                SessionError(this, args);
            }
        }

        /// <summary>
        /// Fires the "field conflict detected" event.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="fieldName">Field name on the source work item</param>
        /// <param name="revisions">Conflicting revisions</param>
        /// <param name="values">Conflicting values</param>
        /// <param name="reaction">Engine's reaction to the event</param>
        internal void FireFieldConflict(
            SyncProcess sharedData,
            string fieldName,
            Pair<FullRevision> revisions,
            Pair<object> values,
            WitConflictReaction reaction)
        {
            if (WorkItemFieldConflict != null)
            {
                FieldConflictEventArgs args = new FieldConflictEventArgs(
                    sharedData.StoreTypes.Left, 
                    fieldName,
                    revisions,
                    values,
                    reaction);

                WorkItemFieldConflict(this, args);
            }
        }

        /// <summary>
        /// Fires the "attachment collections conflict" event
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="ids">Work item ids</param>
        /// <param name="reaction">Engine's reaction to the conflict</param>
        internal void FireAttachmentConflict(
            SyncProcess sharedData,
            Pair<FullId> ids,
            WitConflictReaction reaction)
        {
            if (WorkItemAttachmentsOutOfSync != null)
            {
                string msg = string.Format(Resources.Culture, Resources.MsgAttachmentsOutOfSync, ids.Left, ids.Right);
                CollectionEventArgs args = new CollectionEventArgs(sharedData.StoreTypes.Left, ids, reaction, msg);
                WorkItemAttachmentsOutOfSync(this, args);
            }
        }

        /// <summary>
        /// Fires the "link collections conflict" event
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="ids">Work item ids</param>
        /// <param name="reaction">Engine's reaction to the conflict</param>
        internal void FireLinkConflict(
            SyncProcess sharedData,
            Pair<FullId> ids,
            WitConflictReaction reaction)
        {
            if (WorkItemLinksOutOfSync != null)
            {
                string msg = string.Format(Resources.Culture, Resources.MsgLinksOutOfSync, ids.Left, ids.Right);
                CollectionEventArgs args = new CollectionEventArgs(sharedData.StoreTypes.Left, ids, reaction, msg);
                WorkItemLinksOutOfSync(this, args);
            }
        }

        /// <summary>
        /// Fires the "work item migration failed" event.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="sourceId">Source work item</param>
        /// <param name="e">Exception</param>
        internal void FireItemMigrationFailed(
            SyncProcess sharedData,
            FullId sourceId,
            Exception e)
        {
            m_syncProcess.Stats.IncrementFailedItems();
            if (WorkItemMigrationFailed != null)
            {
                WorkItemMigrationEventArgs args = new WorkItemMigrationEventArgs(
                    sharedData.StoreTypes.Left,
                    sourceId, 
                    string.Format(Resources.Culture, Resources.MsgItemMigrationFailed, sourceId),
                    e);
                WorkItemMigrationFailed(this, args);
            }

            if (WorkItemMigrationComplete != null)
            {
                MigrationCompleteEventArgs args = new MigrationCompleteEventArgs(sharedData.StoreTypes.Left, sourceId, e);
                WorkItemMigrationComplete(this, args);
            }
        }

        /// <summary>
        /// Fires the "missing user" event.
        /// </summary>
        /// <param name="store">Work item store where the user is missing</param>
        /// <param name="userName">Name of the missing user</param>
        internal void FireMissingUser(
            IMigrationWorkItemStore store,
            string userName)
        {
            if (MissingUser != null)
            {
                MissingUserEventArgs args = new MissingUserEventArgs(m_id, store, userName);
                MissingUser(this, args);
            }
        }

        /// <summary>
        /// Processes session-level errors.
        /// </summary>
        /// <param name="e">Exception</param>
        private void OnSessionError(
            Exception e)
        {
            FireSessionError(e);
            m_syncProcess = null;
            m_state = SessionState.Aborted;
        }

        /// <summary>
        /// Completes the session.
        /// </summary>
        private void OnSessionComplete()
        {
            FireEvent(SessionComplete, MigrationToolkitResources.SessionComplete);
            m_syncProcess.Dispose();
            m_syncProcess = null;
            m_state = SessionState.Complete;
        }


        /// <summary>
        /// Implementation of Abort method.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void AbortImpl()
        {
            try
            {
                m_syncProcess.Abort();
                FireEvent(SessionAborted, MigrationToolkitResources.SessionAborted);
                m_syncProcess.Dispose();
                m_syncProcess = null;
                m_state = SessionState.Aborted;
            }
            catch (Exception e)
            {
                OnSessionError(e);
            }
        }

        /// <summary>
        /// Default field value transformer.
        /// </summary>
        class DefaultValueTransformer : IFieldValueTransformer
        {
            #region IFieldValueTransformer Members

            /// <summary>
            /// Gets transformed TFS field value.
            /// </summary>
            /// <param name="fields">Fields colllection</param>
            /// <param name="fieldName">Field name</param>
            /// <returns>Transformed field value</returns>
            public object GetTransformedValue(WIT.FieldCollection fields, WIT.Field field, object value)
            {
                return value;
            }

            #endregion
        }

        /// <summary>
        /// Callback method for custom settings validators.
        /// </summary>
        /// <param name="session">Context session</param>
        /// <param name="tfsStore">TFS store</param>
        internal delegate void SettingsValidationMethod(WorkItemTrackingSession session, WIT.WorkItemStore tfsStore);

        /// <summary>
        /// Gets/sets TFS settings verifier (used by unit tests only!)
        /// </summary>
        internal SettingsValidationMethod TfsSettingsVerifier { get; set; }
    }
}
